package org.bukkit.pm.rspawn;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Event.Priority;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;

/**
 * RSpawn manager for Bukkit
 *
 * @author patamau
 */
public class RSpawnPlugin extends JavaPlugin {
	private final static Logger LOGGER = Logger.getLogger("Minecraft");
	private final static String PROPS_FILE = "rspawn.properties";
	
	private final Random random = new Random();
    private final RSpawnPlayerListener _playerListener = new RSpawnPlayerListener(this);
    private final HashMap<String, RSpawnPlayer> _players = new HashMap<String, RSpawnPlayer>();
    
    private boolean _modified; //if the spawn list was modified
    private int _wlimit; //world size for random location generation (square limit) (100 = +-50)
    public static String VERSION;
    
    public RSpawnPlugin(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) {
        super(pluginLoader, instance, desc, folder, plugin, cLoader);
    }
    
    public void log(Level level, String message){
    	LOGGER.log(level,"[RSpawn] "+message);
    }

    public void onEnable() {
        // Register our events
        PluginManager pm = getServer().getPluginManager();
        pm.registerEvent(Event.Type.PLAYER_COMMAND, _playerListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.PLAYER_JOIN, _playerListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.PLAYER_TELEPORT, _playerListener, Priority.Normal, this);
        PluginDescriptionFile pdfFile = this.getDescription();
        VERSION = pdfFile.getVersion();
        log(Level.INFO, VERSION+" enabled");
        
        _modified=false; 
        _wlimit=1000;
        loadProperties();
        random.setSeed(System.currentTimeMillis());
    }
    
    private void loadProperties(){
    	Properties p = new Properties();
    	
    	//load from file
    	try {
			p.load(new FileInputStream(PROPS_FILE));
		} catch (FileNotFoundException e) {
			storeProperties();
		} catch (IOException e) {
			log(Level.SEVERE,"IO error while loading properties: using defaults");
			e.printStackTrace();
			return;
		}
		// --- parse keys ---
		String swl = (String)p.get("rs.limit");
		if(swl!=null && swl.length()>0){
			try{
				_wlimit = Integer.parseInt(swl);
			}catch(NumberFormatException e){
				log(Level.WARNING,"wrong world limit number format, using default");
				_modified=true;
			}
		}
		// parse players list
		String sl = (String)p.get("rs.list");
		if(sl!=null && sl.length()>0){
			String[] split = sl.split(";");
			for(String s: split){
				if(s.length()==0) continue;
				RSpawnPlayer pp = RSpawnPlayer.parse(s, this);
				if(pp!=null){
					_players.put(pp.getName(), pp);
				}else{
					_modified=true;
				}
			}
		}
    }
    
    public void storeProperties(){
    	Properties p = new Properties();
    	
    	p.setProperty("rs.limit",Integer.toString(_wlimit));
    	//store player serialized list
    	RSpawnPlayer[] pps = getRSpawnPlayers();
    	StringBuffer sb = new StringBuffer();
    	for(RSpawnPlayer pp: pps){
    		sb.append(pp.serialize());
    		sb.append(";");
    	}
    	//XXX: this string might become too long :?
    	p.setProperty("rs.list", sb.toString());
    	
    	//proceed storing to file
    	try {
			p.store(new FileOutputStream(PROPS_FILE), "RSpawn Properties File");
			_modified=false;
			log(Level.INFO,"properties stored to "+PROPS_FILE);
		} catch (Exception e) {
			log(Level.SEVERE,"unable to store properties");
			e.printStackTrace();
		}
    }
    
    public RSpawnPlayer[] getRSpawnPlayers(){
    	RSpawnPlayer[] pps = _players.values().toArray(new RSpawnPlayer[_players.size()]);
    	return pps;
    }
    
    /**
     * Retrieve a RSpawnPlayer from the local cache
     * If no RSpawnPlayer exists with that name, a new one is created and cached
     * @param name
     * @return
     */
    public RSpawnPlayer getRSpawnPlayer(Player player){
    	if(player==null) return null;
    	String name = player.getName();
    	RSpawnPlayer pp = _players.get(name);
    	if(pp==null){
    		pp = new RSpawnPlayer(name, RSpawnPlayer.T_DEF, 0,0,0);
    		_players.put(name, pp);
    		log(Level.FINE,"player "+name+" inserted in rspawn list");
    		_modified=true;
    	}
    	return pp;
    }
    
    /**
     * Removes all rankings
     */
    public void reset(){
    	_players.clear();
    	_modified=true;
    	broadcast("data cleared!");
    }
    
    /**
     * Used to broadcast messages to all the connected players
     * @param message
     */
    public void broadcast(String message){
    	message = "[RSpawn] "+message;
    	for(Player p: this.getServer().getOnlinePlayers()){
    		p.sendMessage(message);
    	}
    }
    
    /**
     * Store properties
     */
    public void onDisable() {
       log(Level.INFO,"disabled");
       if(_modified){
    	   storeProperties();
       }
    }
    
    /**
     * Wrapped to call onDisable before being destroyed
     */
    public void finalize(){
    	onDisable();
    	try {
			super.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
    }
    
    public void setDefaultSpawn(Player player){
    	RSpawnPlayer r = getRSpawnPlayer(player);
		r.setType(RSpawnPlayer.T_DEF);
		_modified=true;
    }

	public void setHomeSpawn(Player player, Location location) {
		RSpawnPlayer r = getRSpawnPlayer(player);
		r.setLocation(location);
		r.setType(RSpawnPlayer.T_HOME);
		_modified=true;
	}
	
	public void setRandomSpawn(Player player){
		RSpawnPlayer r = getRSpawnPlayer(player);
		r.setType(RSpawnPlayer.T_RAND);
		_modified=true;
	}

	public Location getSpawnLocation(Player player) {
		RSpawnPlayer r = getRSpawnPlayer(player);
		switch(r.getType()){
			default:
			case RSpawnPlayer.T_DEF:
				//returning null will prevent teleporting after respawn
				return null;
			case RSpawnPlayer.T_RAND:
				int rx, rz, ry;
				do{
					rx = random.nextInt(_wlimit)-(_wlimit/2);
					rz = random.nextInt(_wlimit)-(_wlimit/2);
					//force loading destination chunk
					player.getWorld().loadChunk(player.getWorld().getChunkAt(rx, rz));
					ry = player.getWorld().getHighestBlockYAt(rx, rz);
					//print warning message if no block can be found in this chunk
					if(ry==0){
						log(Level.WARNING,"unable to find a good spawn block at ("+rx+","+rz+"): trying again...");
					}
					//XXX: there is no reason to manually quit the loop, as a good location will be found for sure
				}while(ry==0);				
				ry++;
				Block b = player.getWorld().getBlockAt(rx,ry,rz);
				log(Level.INFO,"random spawn found at ("+rx+","+ry+","+rz+")");
				//return the block location
				return b.getLocation();
			case RSpawnPlayer.T_HOME:
				//return the recorded location
				return r.getLocation(player);
		}
	}
}
